<?php
#
# VERSION: 2.0a
# AUTHOR: Petko Petkov | pdp (architect)
# HOMEPAGE: http://www.gnucitizen.org
#

#
# DEFAULT CONFIG
#
$SESSION_NAME = 'SESSIONID';
$SESSION_BACKEND = 'FileSystemBackend';
$SESSION_CONFIG = array('session_folder' => '.htsessions', 'session_expiry' => 60);

#
# GLOBALS
#
$BACKEND = null;
$REFERRER = null;

#
# INITIALIZE
#
header('Content-Type: text/javascript');

if (file_exists(dirname(__FILE__).'/config.php'))
	require_once dirname(__FILE__).'/config.php';

if (!class_exists($SESSION_BACKEND))
	require_once $SESSION_BACKEND.'.php';

$BACKEND = new $SESSION_BACKEND();
$BACKEND->configure($SESSION_CONFIG);

session_set_save_handler(array(&$BACKEND, '__open'), array(&$BACKEND, '__close'), array(&$BACKEND, '__read'), array(&$BACKEND, '__write'), array(&$BACKEND, '__destroy'), array(&$BACKEND, '__trash')); 
session_name($SESSION_NAME);
session_start();

$REFERRER = isset($_GET['referrer'])?$_GET['referrer']:(isset($_SERVER['HTTP_REFERRER'])?$_SERVER['HTTP_REFERRER']:'_');

#
# ENUMERATE CURRENT CLIENT
#
$_SESSION['client_agent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['client_address'] = $_SERVER['REMOTE_ADDR'];

if (!isset($_SESSION['client_queue']) || !is_array($_SESSION['client_queue']))
	$_SESSION['client_queue'] = array();

if (isset($REFERRER) && !isset($_SESSION['client_queue'][$REFERRER]))
	$_SESSION['client_queue'][$REFERRER] = array();

#
# DISPATCH
#
if (isset($_GET['action']) && function_exists('action_'.$_GET['action']))
	echo call_user_func('action_'.$_GET['action']);
else
	echo action_pull();

#
# FINISH
#
session_write_close();

#
# HELPERS
#
function is_assoc($php_val) {
	return is_array($php_val) && array_keys($php_val) !== range(0, sizeof($php_val) - 1);
}

function is_session_id($php_val) {
	return strlen($php_val) == strlen(session_id());
}

function array_trim($php_val) {
	$result = array();

	foreach ($php_val as $val)
		$result[] = trim($val);

	return $result;
}

function export_to_json($php_val) {
	if (is_assoc($php_val)) {
		$tmp = '{';

		foreach ($php_val as $key => $val)
			$tmp .= export_to_json($key).': '.export_to_json($val).', ';

		if (sizeof($php_val) > 0)
			$tmp = substr($tmp, 0, -2);

		return $tmp.'}';
	} else if (is_array($php_val)) {
		$tmp = '[';

		foreach ($php_val as $val)
			$tmp .= export_to_json($val).', ';

		if (sizeof($php_val) > 0)
			$tmp = substr($tmp, 0, -2);

		return $tmp.']';
	} else
		return "'".str_replace("'", "\'", stripslashes($php_val))."'";
}

#
# ACTIONS
#
function action_push() {
	global $BACKEND;
	global $REFERRER;

	if (!isset($_GET['client']) || !isset($_GET['message']))
		return;

	$current_session = $_SESSION;

	foreach (array_trim(explode(',', $_GET['client'])) as $client) {
		$_SESSION = array();

		session_decode($BACKEND->read($client));

		if (isset($REFERRER))
			$_SESSION['client_queue'][$REFERRER][] = $_GET['message'];
		else
			$_SESSION['client_queue']['_'][] = $_GET['message'];

		$BACKEND->write($client, session_encode());
	}

	$_SESSION = $current_session;
}

function action_pull() {
	global $REFERRER;

	$php_val = '';

	if (isset($REFERRER) && sizeof($_SESSION['client_queue'][$REFERRER]) > 0)
		$php_val = array_shift($_SESSION['client_queue'][$REFERRER]);
	else if (sizeof($_SESSION['client_queue']['_']) > 0)
		$php_val = array_shift($_SESSION['client_queue']['_']);

	if (isset($_GET['callback']))
		return $_GET['callback'].'('.export_to_json($php_val).');';

	return stripslashes($php_val);
}

function action_list() {
	global $BACKEND;

	if (isset($_GET['callback']))
		return $_GET['callback'].'('.export_to_json($BACKEND->enum()).');';

	return export_to_json($BACKEND->enum());
}

function action_enum() {
	global $BACKEND;

	$current_session = $_SESSION;

	$sessions = array();

	foreach ($BACKEND->enum() as $session_id) {
		$_SESSION = array();

		session_decode($BACKEND->read($session_id));

		$sessions[$session_id] = $_SESSION;
	}

	$_SESSION = $current_session;

	if (isset($_GET['callback']))
		return $_GET['callback'].'('.export_to_json($sessions).');';

	return export_to_json($sessions);
}

function action_view() {
	global $BACKEND;

	if (!isset($_GET['client']))
		return;

	$current_session = $_SESSION;

	$sessions = array();

	foreach (array_trim(explode(',', $_GET['client'])) as $client) {
		$_SESSION = array();

		session_decode($BACKEND->read($client));

		$sessions[$client] = $_SESSION;
	}

	$_SESSION = $current_session;

	if (isset($_GET['callback']))
		return $_GET['callback'].'('.export_to_json($sessions).');';

	return export_to_json($sessions);
}

function action_self() {
	$data = array('self' => session_id());

	if (isset($_GET['callback']))
		return $_GET['callback'].'('.export_to_json($data).');';

	return export_to_json($data);
}

function action_save() {
	global $BACKEND;

	if (!isset($_GET['client']) || !isset($_GET['name']) || !isset($_GET['value']))
		return;

	$current_session = $_SESSION;

	foreach (array_trim(explode(',', $_GET['client'])) as $client) {
		$_SESSION = array();

		session_decode($BACKEND->read($client));

		$_SESSION[$_GET['name']] = $_GET['value'];

		$BACKEND->write($client, session_encode());
	}

	$_SESSION = $current_session;
}

#
# BACKEND
#
class Backend {
	function configure($config) {
		$this->config = $config;
	}
	
	function open()                              { return true; }
	function close()                             { return true; }

	function enum()                              { return true; }

	function read($session_id)                   { return true; }
	function write($session_id, $session_data)   { return true; }

	function destroy($session_id)                { return true; }
	function trash($session_expiry)              { return true; }

	function __open($path, $session_name)        { return $this->open(); }
	function __close()                           { return $this->close(); }
	function __read($session_id)                 { return $this->read($session_id); }
	function __write($session_id, $session_data) { return $this->write($session_id, $session_data); }
	function __destroy($session_id)              { return $this->destroy($session_id); }
	function __trash($session_expiry)            { return $this->trash($session_expiry); }
}

#
# DEFAULT BACKEND
#
class FileSystemBackend extends Backend {
	function open() {
		$path = dirname(__FILE__).'/'.$this->config['session_folder'];

		if (!is_dir($path))
			mkdir($path);

		return true;
	}

	function close() {
		$this->trash(time());
	
		return true;
	}

	function enum() {
		$sessions = array();

		foreach (glob(dirname(__FILE__).'/'.$this->config['session_folder'].'/*') as $file)
			$sessions[] = substr($file, strlen(dirname(__FILE__).'/'.$this->config['session_folder']) + 1);

		return $sessions;
	}

	function read($session_id) {
		$path = realpath(dirname(__FILE__).'/'.$this->config['session_folder'].'/'.$session_id);

		if (!is_file($path))
			return '';

		if (is_file($path))
			return file_get_contents($path);

		return '';
	}

	function write($session_id, $session_data) {
		$path = dirname(__FILE__).'/'.$this->config['session_folder'].'/'.str_replace('.', '/', $session_id);

		$fd = fopen($path, 'w');

		if (!$fd)
			return false;

		if (flock($fd, LOCK_EX)) {
			fwrite($fd, $session_data);
			flock($fd, LOCK_UN);
		}

		fclose($fd);
	}

	function destroy($session_id) {
		$path = dirname(__FILE__).'/'.$this->config['session_folder'].'/'.$session_id;

		if (is_file($path))
			unlink($path);
	}

	function trash($session_expiry) {
		foreach (glob(dirname(__FILE__).'/'.$this->config['session_folder'].'/*') as $file) {
			if (!is_file($file))
				continue;

			if (filemtime($file) + $this->config['session_expiry'] < $session_expiry)
				unlink($file);
		}
	}
}
?>
